﻿/*
 * The MIT License
 * 
 * Copyright 2008 Jeppe Burchardt and Pimm Hogeling
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package org.dr.actionconsole
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.KeyboardEvent;
	import flash.events.TimerEvent;
	import flash.net.SharedObject;
	import flash.profiler.showRedrawRegions;
	import flash.system.System;
	import flash.ui.Keyboard;
	import flash.utils.ByteArray;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import org.dr.actionconsole.data.CommandData;
	import org.dr.actionconsole.events.ConsoleEvent;
	import org.dr.actionconsole.view.DefaultConsoleView;
	import org.dr.actionconsole.view.IConsoleView;
	
	public class Console
	{
		private static const DEFAULT_CONSOLE_VIEW : Class = DefaultConsoleView;
		
		private static const SHARED_OBJECT_NAME : String = "ActionConsole";
		
		private static const NEW_LINE : String = "\n";
		
		private static const CHAR_SET : String = "unicode";
		
		private static var _shortcutKeyCode : uint = 0x4B;
		/**
		 * Bit 0: initialized
		 * Bit 1: consoleView instantiated
		 * Bit 2: consoleView opened
		 * Bit 3: use internal writebuffer
		 * Bit 4: persistence
		 * Bit 5: heuristic input event dispatching
		 * Bit 6: write package
		 * Bit 7: write name
		 * Bit 8: write input
		 * Bit 9: write time before lines
		 * Bit 10: write time before events
		 * Bit 11-31: empty
		 */
		private static var bits : uint = 0x100;
		private static var commands : Array;
		private static var consoleView : IConsoleView;
		private static var consoleViewClass : Class;
		private static var eventDispatcher : EventDispatcher;
		private static var history : Array;
		private static var historyPosition : int;
		private static var memoryTimer : Timer;
		private static var shared : SharedObject;
		private static var stage : Stage;
		private static var writeBuffer : ByteArray;
		private static var xmlIndent : uint;
		
		/**
		 * Console is a static class and should not be instantiated. See the wiki page for more details:
		 *  http://code.google.com/p/actionconsole/wiki/Usage
		 */
		public function Console() : void
		{
			throw new Error("Console is a static class and should not be instantiated. See the wiki page for more details: http://code.google.com/p/actionconsole/wiki/Usage");
		}
		/**
		 * Adds a command to the internal command list of the console, so the listener is notified when a certain command is
		 *  inputten by the user.
		 * 
		 * @param match	<String> This word, when typed in console, will trigger the listener parameter.
		 * @param listener	<Function> The listener function that will be triggered by typing the match parameter in console.
		 *  If this function accepts an Array object as it's only parameter this Array object will contain all arguments that
		 *  were inputted into the console as String objects. Even if you are not going to use these arguments in your code, it
		 *  is recommended that this listener accepts the argument Array object anyway.
		 * @param help	<String (default = null)> A help line that is printed when the help command is used.
		 * @param argumentTags	<Array (default = null)> This Array object should contain String objects that describe the function
		 *  of all the arguments that are expected. This will be used to print the usage when the help command is used.
		 * @param useWeakReference	<Boolean (default = false)> Determines whether the reference to the listener is strong or
		 *  weak. A strong reference (the default) prevents your listener from being garbage-collected. A weak reference does
		 *  not. Class-level member functions are not subject to garbage collection, so you can set useWeakReference to true for
		 *  class-level member functions without subjecting them to garbage collection. If you set useWeakReference to true for
		 *  a listener that is a nested inner function, the function will be garbage-collected and no longer persistent. If you
		 *  create references to the inner function (save it in another variable) then it is not garbage-collected and stays 
		 *  persistent.
		 */
		public static function addCommand(match:String, listener:Function, help:String = null, argumentTags:Array = null, useWeakReference:Boolean = false) : void
		{
			if (bits & 1)
			{
				commands.push(new CommandData(match, listener, help, argumentTags, useWeakReference));
			}
		}
		/**
		 * Registers an event listener object with an EventDispatcher object so that the listener
		 *  receives notification of an event. You can register event listeners on all nodes in the
		 *  display list for a specific type of event, phase, and priority.
		 *
		 * @param type	<String> The type of event.
		 * @param listener	<Function> The listener function that processes the event. This function must accept an Event
		 *  object as its only parameter and must return void.
		 * @param useCapture	<Boolean (default = false)> Determines whether the listener works in the capture phase or the
		 *  target and bubbling phases. If useCapture is set to true, the listener processes the event only during the capture
		 *  phase and not in the target or bubbling phase. If useCapture is false, the listener processes the event only during
		 *  the target or bubbling phase. To listen for the event in all three phases, call addEventListener twice, once with
		 *  useCapture set to true, then again with useCapture set to false.
		 * @param priority	<int (default = 0)> The priority level of the event listener. The priority is designated by a signed
		 *  32-bit integer. The higher the number, the higher the priority. All listeners with priority n are processed before
		 *  listeners of priority n-1. If two or more listeners share the same priority, they are processed in the order in 
		 *  which they were added. The default priority is 0.
		 * @param useWeakReference	<Boolean (default = false)> Determines whether the reference to the listener is strong or
		 *  weak. A strong reference (the default) prevents your listener from being garbage-collected. A weak reference does
		 *  not. Class-level member functions are not subject to garbage collection, so you can set useWeakReference to true for
		 *  class-level member functions without subjecting them to garbage collection. If you set useWeakReference to true for
		 *  a listener that is a nested inner function, the function will be garbage-collected and no longer persistent. If you
		 *  create references to the inner function (save it in another variable) then it is not garbage-collected and stays 
		 *  persistent.
		 */
		public static function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false) : void
		{
			if (!eventDispatcher)
			{
				eventDispatcher = new EventDispatcher();
			}
			eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		/**
		 * Closes the console. Does nothing if the console was not opened.
		 */
		public static function close() : void
		{
			if (bits & 4) // If the console is opened.
			{
				_close();
			}
		}
		/**
		 * Returns true if the console has been initialized using the initialize method, and false if not.
		 * 
		 * @return	A Boolean object representing whether the console has been initialized.
		 */
		public static function getInitialized() : Boolean
		{
			return Boolean(bits & 1);
		}
		/**
		 * Returns true if the console is opened, and false if not.
		 * 
		 * @return	A Boolean object representing whether the console is opened.
		 */
		public static function getOpened() : Boolean
		{
			return Boolean(bits & 4);
		}
		/**
		 * Checks whether the EventDispatcher object has any listeners registered for a specific type
		 *  of event. This allows you to determine where an EventDispatcher object has altered
		 *  handling of an event type in the event flow hierarchy. To determine whether a specific
		 *  event type actually triggers an event listener, use willTrigger().
		 *
		 * @param type	<String> The type of event.
		 * @return	A value of true if a listener of the specified type is registered; false otherwise.
		 */
		public static function hasEventListener(type:String) : Boolean
		{
			if (eventDispatcher)
			{
				return eventDispatcher.hasEventListener(type);
			}
			return false;
		}
		/**
		 * Determines whether the console should dispatch ConsoleEvent.CONSOLE_INPUT events heuristic. This means that these events
		 *  will be dispatched before the console checks the internal command list. When set to false (the default) the console
		 *  will check the internal command list when a user commits an input, and only dispatch a ConsoleEvent.CONSOLE_INPUT event
		 *  only if no matching internal command is found.
		 * 
		 * @default false
		 */
		public static function get heuristicInputEventDispatching() : Boolean
		{
			return Boolean(bits & 0x200);
		}
		public static function set heuristicInputEventDispatching(value:Boolean) : void
		{
			if (value !== heuristicInputEventDispatching)
			{
				bits ^= 0x20;
			}
		}
		/**
		 * Initializes the console so it can be opened using the keyboard binding; and the writeLine, write, writeXML and writeEvent
		 *  methods can be used. Before this method is called the console will be useless, but will not use a lot of memory. Note that
		 *  all writeLine, write, writeXML and writeEvent method calls will do nothing if this method isn't called first.
		 * 
		 * @param _stage	<Stage> The stage, this is used to add the console to the screen, and to listen to the keyboard events. If
		 *  this is an AIR project you might have multiple Stage objects, use the one that you would like to open and see ActionConsole
		 *  in.
		 * @param useBuffer	<Boolean (default = true)> Determines whether the writebuffer functionality should be used. If this is
		 *  set to true the console will store any data passed using the writeLine, write, writeXML and writeEvent methods in the
		 *  writebuffer so they can be written to the console when it is first opened. Setting this to false will save memory, but
		 *  any writeLine, write, writeXML and writeEvent call will do nothing unless the console was opened first.
		 * @param writeDefaultBuffer <Boolean (default = true)> Write the default "Welcome to ActionConsole! ActionConsole is a FOSS
		 *  input/output console [...]" message to the writebuffer (even if the useBuffer parameter is set to false).
		 * @param consoleView	<Class (default = null)> The class that should be used to display the console. This class must implement
		 *  the IConsoleView class. Set to null (the default) to use the DefaultConsoleView class.
		 */
		public static function initialize(_stage:Stage, useBuffer:Boolean = true, writeDefaultBuffer:Boolean = true, consoleView:Class = null) : void
		{
			if (!(bits & 1) && _stage) // If not already initialized. Initializing it more than once could result in a memory leak because of the addEventListener call or in unexpected behavior from the console because some properties are set.
			{
				history = new Array();
				stage = _stage;
				consoleViewClass = consoleView ? consoleView : DEFAULT_CONSOLE_VIEW;
				commands = DEFAULT_COMMANDS;
				writeBuffer = writeDefaultBuffer ? DEFAULT_WRITE_BUFFER : new ByteArray();
				shared = SharedObject.getLocal(SHARED_OBJECT_NAME);
				stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown, false, int.MAX_VALUE, false); // int.MAX_VALUE because the console should be the first thing to react to a down key.
				bits ^= 1;
				if (useBuffer)
				{
					bits ^= 0x8;
				}
				if (shared.data.visible == true)
				{
					_open();
				}
				memoryTimer = new Timer(0);
				memoryTimer.addEventListener(TimerEvent.TIMER, memoryTick);
			}
		}
		/**
		 * Opens the console. Does nothing if the console is already opened.
		 */
		public static function open() : void
		{
			if ((bits & 5) === 1) // If the console is closed and initialized.
			{
				_open();
			}
		}
		/**
		 * Determines whether the console should store the current state of the console - opened or closed - in a SharedObject object.
		 *  If this is set to true, and the console is still opened when the Flash application is closed the console will
		 *  automatically open the next time it is initialized.
		 */
		public static function get persistence() : Boolean
		{
			return Boolean(bits & 8);
		}
		public static function set persistence(value:Boolean) : void
		{
			if (value !== persistence)
			{
				bits ^= 8;
			}
			if (!value) // If set to false.
			{
				shared.data.visible = false;
			}
		}
		/**
		 * Adds an alias to a command. Typing this alias in the console will then trigger the same listener as the original
		 *  command does. There is no adding aliasses to unexisting commands.
		 * 
		 * @param alias	<String> This word, when typed in console, will trigger the same listener as match.
		 * @param match	<String> This should be the match of an existing command, or an alias for an existing command.
		 */
		public static function registerAlias(alias:String, match:String) : void
		{
			for each (var command : CommandData in commands)
			{
				if (command.matches(match))
				{
					command.addAlias(alias);
					break;
				}
			}
		}
		/**
		 * Removes a command from the internal command list. Make sure the match parameter is the same as the one that
		 *  was used while adding the command. You cannot remove a command by passing an alias of that command.
		 * 
		 * @param match	<String> The same match that was used when adding the command that should be removed.
		 */
		public static function removeCommand(match:String) : void
		{
			var i : int = -1;
			var l : uint = commands.length;
			while (++i < l)
			{
				if ((commands[i] as CommandData).id === match)
				{
					commands.splice(i, 1);
					--l;
				}
			}
		}
		/**
		 * Removes a listener from the EventDispatcher object. If there is no matching listener registered with the EventDispatcher
		 *  object, a call to this method has no effect.
		 *
		 * @param type	<String> The type of event.
		 * @param listener	<Function> The listener object to remove.
		 * @param useCapture	<Boolean (default = false)> Specifies whether the listener was registered for the capture phase or the
		 *  target and bubbling phases. If the listener was registered for both the capture phase and the target and bubbling phases,
		 *  two calls to removeEventListener() are required to remove both, one call with useCapture set to true, and another call
		 *  with useCapture set to false.
		 */
		public static function removeEventListener(type:String, listener:Function, useCapture:Boolean=false) : void
		{
			if (!eventDispatcher)
			{
				return;
			}
			eventDispatcher.removeEventListener(type, listener, useCapture);
		}
		/**
		 * The console will open and initialize when this keycode is pressed along with CTRL.
		 * 
		 * @default 0x4B (K button)
		 */
		public static function get shortcutKeyCode() : uint
		{
			return _shortcutKeyCode;
		}
		public static function set shortcutKeyCode(value:uint) : void
		{
			_shortcutKeyCode = value;
		}
		/**
		 * This method returns the word "ActionConsole".
		 * 
		 * @return <String> A String object containing the word "ActionConsole".
		 */
		public static function toString() : String
		{
			return "ActionConsole";
		}
		public function toString() : String
		{
			return Console.toString();
		}
		/**
		 * Removes an alias from a command.
		 * 
		 * @param alias	<String> The alias that has to be removed.
		 */
		public static function unregisterAlias(alias:String) : void
		{
			for each (var command:CommandData in commands)
			{
				if (command.removeAlias(alias))
				{
					return;
				}
			}
		}
		/**
		 * Checks whether an event listener is registered with this EventDispatcher object or any of
		 *  its ancestors for the specified event type. This method returns true if an
		 *  event listener is triggered during any phase of the event flow when an event of the
		 *  specified type is dispatched to this EventDispatcher object or any of its descendants.
		 *
		 * @param type	<String> The type of event.
		 * @return	A value of true if a listener of the specified type will be triggered; false otherwise.
		 */
		public static function willTrigger(type:String) : Boolean
		{
			if (eventDispatcher)
			{
				return eventDispatcher.willTrigger(type);
			}
			return false;
		}
		/**
		 * Writes text to the console. You can pass as many arguments to this method as you like. If you
		 *  pass a ConsoleTextColor object as an argument all text that is printed after that argument will
		 *  be in that supplied color. Any other objects will be converted to String objects, and written to
		 *  the console. In contrast to the writeLine method, text written to the console by this method will
		 *  not be followed by a linebreak automatically.
		 */
		public static function write(...arguments) : void
		{
			if (bits & 1)
			{
				writeArguments(arguments);
			}
		}
		/**
		 * Writes an Event object to the console. If possible it will be formatted automatically. Because this
		 *  method accepts an Event object as its only argument it can be used as a listener with event dispatchers.
		 *  This can be done like this: eventDispatcher.addEventListener("some event", Console.writeEvent);
		 */
		public static function writeEvent(e:Event) : void
		{
			if (!e || !(bits & 1))
			{
				return;
			}
			const eventString : String = e.toString();
			// If it's not a regular event string (built with a formatToString call) we'll just writeLine it
			if (!(eventString.charCodeAt(0) === 91 && eventString.charCodeAt(eventString.length - 1) === 93))
			{
				writeLine(eventString);
				return;
			}
			const eventDetails : Array = eventString.substring(1, eventString.length - 1).split(" ");
			const eventClassName : String = getQualifiedClassName(e);
			const eventClass : Class = getDefinitionByName(eventClassName) as Class;
			if (bits & 0x400)
			{
				_write(getTime() + " ", ConsoleTextColor.NORMAL);
			}
			_write(eventDetails[0] + " type=", ConsoleTextColor.BOLD);
			var details : String = "";
			var i : uint = 0;
			const l : uint = eventDetails.length;
			while (++i < l)
			{
				var detailArray : Array = eventDetails[i].split("=", 2);
				if (detailArray[0] === "type")
				{
					// Build the "caps style" type string
					var type : String = detailArray[1];
					var capsType : String = "";
					var j : uint = 0;
					var m : uint = type.length - 1;
					while (++j < m)
					{
						var char : String = type.charAt(j);
						if (char.toUpperCase() === char)
						{
							capsType += "_" + char;
						}
						else
						{
							capsType += char;
						}
					}
					if (eventClass[capsType.toUpperCase()])
					{
						// If the "caps style" type string exists in the class, we write the that string
						_write(eventClassName.split("::", 2)[1] + "." + capsType.toUpperCase(), ConsoleTextColor.BOLD);
					}
					else
					{
						// Otherwise we write the normal string that we found in the eventString property
						_write(type, ConsoleTextColor.BOLD);
					}
				}
				else
				{
					details += " " + eventDetails[i];
				}
			}
			_write(details + NEW_LINE, ConsoleTextColor.NORMAL);
		}
		/**
		 * Determines whether the console should print out the input by the user directly.
		 * 
		 * @default true
		 */
		public static function get writeInput() : Boolean
		{
			return Boolean(bits & 0x100);
		}
		public static function set writeInput(value:Boolean) : void
		{
			if (value !== writeInput)
			{
				bits ^= 0x100;
			}
		}
		/**
		 * Writes text to the console, followed by a linebreak. You can pass as many arguments to this method
		 *  as you like. If you pass a ConsoleTextColor object as an argument all text that is printed after
		 *  that argument will be in that supplied color. Any other objects will be converted to String objects,
		 *  and written to the console. If the linebreak is undesired, use the write method instead. The
		 *  behaviour of this method can be modified using the static writeTimeBeforeLines, writePackage and
		 *  writeName properties.
		 */
		public static function writeLine(...arguments) : void
		{
			if (bits & 1)
			{
				var prefix : String = new String();
				if (bits & 0x200)
				{
					prefix += getTime();
				}
				if (bits & 0xC0)
				{
					try
					{
						try
						{
							throw new Error("Fake error");
						}
						catch (e:Error)
						{
							if (e.getStackTrace())
							{
								var stacks : Array = e.getStackTrace().split("\n")[2].substring(4).split("[")[0].split("::");
								
								if (stacks[1].slice(0, -2) == "apply" || stacks[1].slice(0, -2) == "call")
								{
									stacks = e.getStackTrace().split("\n")[4].substring(4).split("[")[0].split("::");
								}
								
								if (stacks.length === 1)
								{
									stacks.unshift("[No package]");
								}
								if (bits & 0x40)
								{
									prefix += stacks[0];
									if (bits & 0x80)
									{
										prefix += ".".concat(stacks[1].slice(0, -2));
									}
								}
								else
								{
									prefix += stacks[1].slice(0, -2);
								}
								prefix += ":";
							}
						}
					}
					catch (e:Error)
					{
						prefix = "Unknown:";
					}
				}
				arguments.unshift(prefix);
				arguments.push(NEW_LINE);
				writeArguments(arguments);
			}
		}
		/**
		 * Determines whether the console should print the package of the class that calls the writeLine method.
		 * 
		 * @default false
		 */
		public static function get writeName() : Boolean
		{
			return Boolean(bits & 0x80);
		}
		public static function set writeName(value:Boolean) : void
		{
			if (value !== writeName)
			{
				bits ^= 0x80;
			}
		}
		/**
		 * Determines whether the console should print the package of the class that calls the writeLine method.
		 * 
		 * @default false
		 */
		public static function get writePackage() : Boolean
		{
			return Boolean(bits & 0x40);
		}
		public static function set writePackage(value:Boolean) : void
		{
			if (value !== writePackage)
			{
				bits ^= 0x40;
			}
		}
		/**
		 * Determines whether the console should print the time that has elepsed since Flash Player was initialized in front
		 *  of an Event object written with the writeEvent method. For time in front of messages written with the writeLine
		 *  method, see writeTimeBeforeLine.
		 * 
		 * @default false
		 */
		public static function get writeTimeBeforeEvents() : Boolean
		{
			return Boolean(bits & 0x400);
		}
		public static function set writeTimeBeforeEvents(value:Boolean) : void
		{
			if (value !== writeTimeBeforeEvents)
			{
				bits ^= 0x400;
			}
		}
		/**
		 * Determines whether the console should print the time that has elepsed since Flash Player was initialized in front
		 *  of a message written with the writeLine method. For time in front of Event objects written with the writeEvent
		 *  method, see writeTimeBeforeEvent.
		 * 
		 * @default false
		 */
		public static function get writeTimeBeforeLines() : Boolean
		{
			return Boolean(bits & 0x200);
		}
		public static function set writeTimeBeforeLines(value:Boolean) : void
		{
			if (value !== writeTimeBeforeLines)
			{
				bits ^= 0x200;
			}
		}
		/**
		 * Writes XML data to the console. The supplied argument should be of the XML type, the XMLDocument type, the
		 *  XMLNode type or the String type (providing this String object has XML as it's value). If a XMLList object
		 *  is passed, the first value in this XMLList object will be written.
		 */
		public static function writeXML(xml:*) : void
		{
			var node : XML;
			switch(getQualifiedClassName(xml))
			{
				case "XML":
				node = xml;
				break;
				case "XMLList":
				node = xml[0];
				break;
				case "flash.xml::XMLNode":
				case "flash.xml::XMLDocument":
					xml = xml.toString();
				case "String":
				try {
					node = new XML(xml);
				} catch (e:Error) {
					_write("A String object that does not contain correct XML data has been passed to the writeXML method\n", ConsoleTextColor.ERROR);
				}
				break;
				default:
				_write("An unrecognized object has been passed to the writeXML method\n", ConsoleTextColor.ERROR);
				return;
			}
			if (node) {
				writeXMLNode(node, true);
			}
		}
		private static function _close() : void
		{
			bits ^= 4;
			if (bits & 0x10)
			{
				shared.data.visible = new Boolean();
			}
			consoleView.setOpened(false);
		}
		private static function _open() : void
		{
			if (!(bits & 2)) // If the consoleView is not instantiated.
			{
				consoleView = new consoleViewClass();
				stage.addChild(consoleView.getDisplayObject());
				stage.addEventListener(Event.ADDED, siblingAdded, false, int.MIN_VALUE, false); // int.MIN_VALUE because the console should be the last thing to react to a ADDED event.
				bits ^= 2;
				writeBuffer.position = 0;
				while (writeBuffer.bytesAvailable)
				{
					consoleView.write(writeBuffer.readMultiByte(writeBuffer.readUnsignedInt(), CHAR_SET), writeBuffer.readUnsignedInt());
				}
				writeBuffer.length = 0;
				writeBuffer = null;
				if (hasEventListener(ConsoleEvent.CONSOLE_FIRST_OPENED)) {
					eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.CONSOLE_FIRST_OPENED));
				}
			}
			bits ^= 4;
			if (bits & 0x10)
			{
				shared.data.visible = new Boolean(true);
			}
			consoleView.setOpened(true);
		}
		private static function _write(text:String, color:ConsoleTextColor) : void
		{
			if (bits & 2) // If the consoleView is instantiated.
			{
				consoleView.write(text, color.uintColorCode); // Write to console.
			}
			else if (bits & 8) // If the console is set to use the writebuffer.
			{
				var bufferText:ByteArray = new ByteArray();
				bufferText.writeMultiByte(text, CHAR_SET);
				writeBuffer.writeUnsignedInt(bufferText.length);
				writeBuffer.writeBytes(bufferText, 0, bufferText.length); // Write to writebuffer, will be written when the console is first opened.
				writeBuffer.writeUnsignedInt(color.uintColorCode);
			}
		}
		private static function get ATTRIBUTE_BACKETS_AND_VALUE() : ConsoleTextColor
		{
			return new ConsoleTextColor(0x7FA1FF);
		}
		private static function convertToString(text:*) : String
		{
			// If the supplied object is null, return "null" (which is a String object, this method itself does not return null).
			if (text === null)
			{
				return "null";
			}
			switch (typeof text)
			{
				// If the supplied object is a String, return it.
				case "string":
				return text;
				// If the supplied object is a Boolean or a Number, return it using it's toString method.
				case "boolean":
				case "number":
				return text.toString();
				// If the supplied object is an XML, return it using it's toXMLString method.
				case "xml":
				return text.toXMLString();
				// If the supplied object is a Function, return "function".
				case "function":
				return "function";
				// If the supplied object is of type undefined (claimed by typeof), return "undefined".
				case "undefined":
				return "undefined";
			}
			switch (getQualifiedClassName(text))
			{
				// If the supplied object is an Array, return the word "Array" as well as all the elements inside the Array.
				case "Array":
				var arrayRepresentation:String = "[Array";
				var i:int = -1;
				// If the supplied Array object has more then eight elements then we will only print out the first eight.
				const l:uint = text.length > 8 ? 8 : text.length;
				while (++i < l) {
					arrayRepresentation += " " + convertToString(text[i]) + (i === l - 1 ? "" : ",");
				}
				// If the supplied Array object has more then eight elements then we add "..." to the end of the String, so the client knows there are more elements in this Array.
				if (text.length > 8)
				{
					arrayRepresentation += ", ...";
				}
				return arrayRepresentation + "]";
				// If the supplied object is of a type not listed above, do the following:
				default:
				// Try to call the toString implementation of this object.
				try
				{
					return text.toString();
				}
				catch (e:Error) {}
				// If that fails, use the toString of the Object class.
				return Object.prototype.toString.call(text);
			}
		}
		private static function get DEFAULT_COMMANDS() : Array
		{
			var defaultCommands : Array = new Array();
			defaultCommands.push(new CommandData("clear", defaultCommandClear, "Clears the console"));
			defaultCommands.push(new CommandData("history", defaultCommandHistory, "Prints all command that are inputted into the console"));
			defaultCommands.push(new CommandData("mem", defaultCommandMem, "Prints the memory use of this application", new Array("interval in ms|\"stop\"")));
			defaultCommands.push(new CommandData("redraw", defaultCommandRedraw, "Turns redrawregions on or off", new Array("\"on\"|\"off\"")));
			defaultCommands.push(new CommandData("help", defaultCommandHelp, "Prints the help and usage of the given command", new Array("command")));
			return defaultCommands;
		}
		private static function get DEFAULT_WRITE_BUFFER() : ByteArray
		{
			var defaultWriteBuffer : ByteArray = new ByteArray();
			var temporaryWriteBuffer : ByteArray = new ByteArray();
			temporaryWriteBuffer.writeMultiByte("Welcome to ActionConsole!", CHAR_SET);
			defaultWriteBuffer.writeUnsignedInt(temporaryWriteBuffer.length);
			defaultWriteBuffer.writeBytes(temporaryWriteBuffer);
			defaultWriteBuffer.writeUnsignedInt(ConsoleTextColor.BOLD.uintColorCode);
			temporaryWriteBuffer.length = 0;
			temporaryWriteBuffer.writeMultiByte(" ActionConsole is a small and effective input/output console for actionscript 3. This project is released under the X11/MIT license. You can obtain a copy of the source on http://actionconsole.googlecode.com/\nCopyright 2008 Jeppe Burchardt and Pimm Hogeling\n\n", CHAR_SET);
			defaultWriteBuffer.writeUnsignedInt(temporaryWriteBuffer.length);
			defaultWriteBuffer.writeBytes(temporaryWriteBuffer);
			defaultWriteBuffer.writeUnsignedInt(ConsoleTextColor.NORMAL.uintColorCode);
			return defaultWriteBuffer;
		}
		private static function defaultCommandClear(args:Array) : void
		{
			consoleView.clearConsole();
		}
		private static function defaultCommandHelp(args:Array) : void
		{
			if (args.length) 
			{
				for each (var c:CommandData in commands)
				{
					if (c.matches(args[0]))
					{
						write(c.help + NEW_LINE, ConsoleTextColor.BOLD, "Usage:", ConsoleTextColor.NORMAL, c.usage + NEW_LINE);
						return;
					}
				}
				write("Unknown command" + NEW_LINE);
			}
			else
			{
				write(ConsoleTextColor.BOLD, "Usage:", ConsoleTextColor.NORMAL, "help [command]" + NEW_LINE);
			}
		}
		private static function defaultCommandHistory(args:Array) : void
		{
			write(ConsoleTextColor.BOLD, "History:", ConsoleTextColor.NORMAL, history.join(", ") + NEW_LINE);
		}
		private static function defaultCommandMem(args:Array) : void
		{
			var interval:uint;
			if (args[0] === "stop")
			{
				memoryTimer.stop();
			}
			else
			{
				try
				{
					interval = parseInt(args[0]);
				}
				catch (e:Error)
				{
				}
				finally
				{
					if (interval)
					{
						memoryTimer.delay = interval;
						memoryTimer.start();
					}
					memoryTick();
				}
			}
		}
		private static function defaultCommandRedraw(args:Array) : void
		{
			switch(args[0])
			{
				case "on":
				showRedrawRegions(true);
				break;
				case "off":
				showRedrawRegions(false);
				break;
			}
		}
		private static function getTime() : String
		{
			return (getTimer() / 1000).toString() + " s:";
		}
		private static function handleCommand(command:String) : void
		{
			// Write input
			if (bits & 0x100)
			{
				write(USER_INPUT, ">>> " + command + NEW_LINE);
			}
			
			// Event listeners in heuristic mode
			if (bits & 0x20 && hasEventListener(ConsoleEvent.CONSOLE_INPUT))
			{
				eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.CONSOLE_INPUT, command));
			}
			
			// Generate the pattern
			var pattern : Array = new Array();
			var target : Boolean = true;
			const split : Array = command.split("\"");
			for each (var part : String in split)
			{
				if (target = !target)
				{
					pattern.push(part);
				}
				else
				{
					var pSplit : Array = part.split(" ");
					for each (var subPart : String in pSplit)
					{
						if (subPart)
						{
							pattern.push(subPart);
						}
					}
				}
			}
			const match : String = pattern[0];
			const args : Array = pattern.slice(1);
			
			pattern = null;
			
			// Internal commands
			for each (var c:CommandData in commands)
			{
				if (c.matches(match))
				{
					var l:Function = c.listener;
					if (l !== null)
					{
						try
						{
							l(args);
						}
						catch (e:Error)
						{
							if (e.errorID === 1063) // If the error is thrown because it was an argument problem.
							{
								try
								{
									l(); // We do our best to still call the listener, so we try to do so with no arguments.
								}
								catch (r:Error)
								{
									throw e;
								}
							}
							else
							{
								throw e;
							}
						}
						return;
					}
					else // l is null, which probably means the listener is garbage collected.
					{
						removeCommand(c.id);
					}
				}
			}
			
			// Event listeners in non-heuristic mode
			if (!(bits & 0x20) && hasEventListener(ConsoleEvent.CONSOLE_INPUT))
			{
				eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.CONSOLE_INPUT, command));
			}
		}
		private static function keyDown(event:KeyboardEvent) : void
		{
			if (event.ctrlKey && event.keyCode === _shortcutKeyCode)
			{
				bits & 4 ? _close() : _open();
			}
			else if (bits & 4) // If the console if opened.
			{
				switch (event.keyCode)
				{
					case Keyboard.ENTER: // Enter key.
					var command : String = consoleView.getInput();
					if (command)
					{
						history.unshift(command);
						historyPosition = -1;
						consoleView.setInput("");;
						handleCommand(command);
					}
					break;
					case Keyboard.ESCAPE:
					_close();
					break;
					case Keyboard.UP:
					if (history.length)
					{
						historyPosition = ++ historyPosition % history.length;
						consoleView.setInput(history[historyPosition]);
					}
					break;
					case Keyboard.DOWN:
					if (history.length)
					{
						historyPosition = (-- historyPosition < 0 ? history.length-1 : historyPosition);
						consoleView.setInput(history[historyPosition]);
					}
					break;
				}
			}
		}
		private static function memoryTick(event:Event = null) : void
		{
			const mem : uint = System.totalMemory;
			write(ConsoleTextColor.BOLD, "System memory: " + String(uint(mem / 1048.576) / 1000) + " MiB", ConsoleTextColor.NORMAL, "(" + mem.toString() + " bytes)" + NEW_LINE);
		}
		private static function get NODE_NAME() : ConsoleTextColor
		{
			return new ConsoleTextColor(0xC97FFF);
		}
		private static function siblingAdded(event:Event) : void
		{
			try
			{
				var sibling : DisplayObject = event.target as DisplayObject;
				if (stage.getChildIndex(sibling) > stage.getChildIndex(consoleView.getDisplayObject()))
				{
					stage.swapChildren(consoleView.getDisplayObject(), sibling);
				}
			}
			catch(e:Error)
			{
				
			}
		}
		private static function get SYSTEM() : ConsoleTextColor
		{
			return new ConsoleTextColor(0x99FF99);
		}
		private static function get USER_INPUT() : ConsoleTextColor
		{
			return new ConsoleTextColor(0x3399FF);
		}
		private static function writeArguments(arguments:Array) : void
		{
			var textColor : ConsoleTextColor = ConsoleTextColor.NORMAL;
			var writeCache : String = new String();
			const l : uint = arguments.length;
			var i : int = -1;
			while (++i < l)
			{
				if (arguments[i] is ConsoleTextColor)
				{
					if (writeCache.length)
					{
						_write(writeCache, textColor);
					}
					textColor = arguments[i];
					writeCache = new String();
				}
				else
				{
					var argument:String = convertToString(arguments[i]);
					if (argument !== "")
					{
						writeCache += convertToString(arguments[i]);
						if (argument.charCodeAt(argument.length -1) !== 10)
						{
							writeCache += " ";
						}
					}
				}
			}
			if (writeCache.length)
			{
				_write(writeCache, textColor);
			}
		}
		private static function writeXMLNode(node:XML, mainFlag:Boolean = false) : void
		{
			var t : uint = 0;
			var breakAndIndent:String = NEW_LINE;
			while (t < xmlIndent)
			{
				breakAndIndent += "\t";
				++t;
			}
			_write((mainFlag ? breakAndIndent.substr(NEW_LINE.length) : breakAndIndent) + "<", ConsoleTextColor.BOLD);
			_write(node.name(), NODE_NAME);
			var attributes : XMLList = node.attributes();
			var l : uint = attributes.length();
			var i : int = -1;
			while (++i < l)
			{
				_write(" " + attributes[i].name() + "=", ConsoleTextColor.BOLD);
				_write("\"" + String(attributes[i]) + "\"", ATTRIBUTE_BACKETS_AND_VALUE);
			}
			if (node.children().length())
			{
				++xmlIndent;
				var m : uint = node.children().length();
				_write(">", ConsoleTextColor.BOLD);
				var j : int = -1;
				while (++j < m)
				{
					var child : XML = node.children()[j];
					switch (child.nodeKind())
					{
						case "element":
						writeXMLNode(child);
						break;
						default:
						var childText : String = NEW_LINE + child;
						childText = childText.split("\r\n").join(NEW_LINE); // Convert all the 'weird' linebreaks to 'normal' ones.
						childText = childText.split(NEW_LINE).join(breakAndIndent + "\t"); // Add the xmlIndent '\t' characters to the String object.
						_write(childText, ConsoleTextColor.NORMAL);
						break;
					}
				}
				--xmlIndent;
				_write(breakAndIndent + "<", ConsoleTextColor.BOLD);
				_write("/", XML_CLOSER);
				_write(node.name(), NODE_NAME);
				_write(mainFlag ? ">" + NEW_LINE : ">", ConsoleTextColor.BOLD);
				
			}
			else
			{
				_write(" /", XML_CLOSER);
				_write(mainFlag ? ">" + NEW_LINE : ">", ConsoleTextColor.BOLD);
			}
		}
		private static function get XML_CLOSER() : ConsoleTextColor
		{
			return new ConsoleTextColor(0xFF7F7F);
		}
	}
}